
// Logging Subsystem
// Author: Vern McGeorge
// Filename: logger.js
// Date Created:	Mar-Jul 08
// Modification:	17 Jan 09 - Generalized from a log file model to log method model that will work in the browser.

// Set up a generic log on callback after window.onload.
function setupGenericLog () {
	lineLimit = 9999;
	maxIndent = 5;
	alertLimit = 0;
	tagLimit = 0;
	doLog = null;
	doNotLog = ["parent", "parentNode", "owner", "attributes", "HTML", "previous", "style", "last"];
	brief = false;
	logStart(toConsole, lineLimit, maxIndent, alertLimit, tagLimit, doLog, doNotLog, brief);
	return true;
}

// Register the setup of a generic log.
jQuery.event.add( window, "load", setupGenericLog );

// setupGenericLog();

// Logging Attributes

var logMaxIndentation;					// Maximum indentation level to prevent content from being pushed off the right edge.
var logIndentation;							// Current indentation level.
var logIndentationText;					// A string from which indentation substrings will be extracted.
var logIndentationString;				// Current indentation string.
var logIndentationMultiplier;		// Indentation times this is the length of the indentation substring.
var logDoFollowPatterns;				// An array of RegExps to match against object members. Only log members that match.
var logDoNotFollowPatterns;			// An array of RegExps to match against object members. If match, to not log in to the member.
var logBrief;										// Default false to log all attributes.
var logVerbose;									// Default true to log all attributes.
var logElementLimit;						// In brief mode, limit the number of Array or Collection elements logged.
var logRecursionList;						// A list of objects logged to prevent circular recursion.
var logRecursionListLength;			// Number of items in the recursion list.
var logBuffer;									// Current content buffer.
var logFile;										// If the context allows (non-client-side JavaScript), a separate log file may be created.
var logMethod;									// A method that will append a new line to the log (e.g. logFile.write). If null, no logging will occur.
var logAlerts;									// Running counter of alerts.
var logAlertLimit;							// The maximum number of alerts to be displayed before they are absorbed silently.
var logTagLimit;								// The maximum number of alerts to be tagged in the log before they are absorbed silently.
var logLines;										// Running counter of logged lines.
var logLineLimit;								// The maximum number of lines to be logged.
	

// What is this stuff?
var logVMAt;
var logDialogLimit;

// Basic fuctions

// Pop up a dialog to open an external log file. If the user clicks the Cancel button, the result is null and no logging will be performed.
// This only works when your program isn't client side JavaScript and thus has access to the file system.
function logOpen (lineLimit, indentationLimit, alertLimit, tagLimit, doFollow, doNotFollow, brief) {
	logFile = File.saveDialog("Save Log File As", undefined);
	logFile.open("w");
	if (logFile != null) {
		logStart (logFile.writeln, lineLimit, indentationLimit, alertLimit, tagLimit, doFollow, doNotFollow, brief)
	}
}

// Start a logger which streams complete lines of output to the file or to a client side function such as toConsole() for the web.
// The specification of lineWriter is mandatory. All other parameters have reasonable defaults.
function logStart (lineWriter, lineLimit, indentationLimit, alertLimit, tagLimit, doFollow, doNotFollow, brief) {
	// alert("logStart a");
	// Set all user specifiable parameters.
	// 1. Required.
	logMethod = lineWriter;
	// 2. Optional.
	logLineLimit = lineLimit ? lineLimit : 9999;
	logMaxIndentation = indentationLimit ? indentationLimit : 5;
	logAlertLimit = alertLimit ? alertLimit : 0;
	logTagLimit = tagLimit ? tagLimit : 0;
	logSetDoFollowPatterns(doFollow);
	logSetDoNotFollowPatterns(doNotFollow);
	logBrief = brief ? brief : false;
	logVerbose = brief ? brief : true;
	// Set the remaining parameters to initial values.
	// 1. Pick a workable indentation string.
	if (logFile == null) {
		logIndentationText = ".___"; // For the web.
	}	else {
		logIndentationText = ".   "; // For files.
	}
	// 2. Figure out how long you need.
	logIndentationMultiplier = logIndentationText.length;
	var maxIndentLength = logMaxIndentation * logIndentationMultiplier;
	// 3. Create a string longer than you need.
	while (logIndentationText.length < maxIndentLength) {
		logIndentationText = logIndentationText + logIndentationText;
	}
	logIndentationString = new String(logIndentationText);
	// 4. all the rest.
	logElementLimit = 2;
	logRecursionList = new Array();
	// What is this?
	logVMAt = 42; // bogus constant for now
	// Set working counters and buffers.
	logIndentation = 0;
	logBuffer = "";
	logRecursionListLength = 0;
	logAlerts = 0;
	logLines = 0;
}

// If the log is started, flush it and stop it.
function logStop () {
	if (null != logMethod) {
//  logLine();
		logToMethod(logBuffer + "\n");
	}
	logMethod = null;
}

// Stop the log and if the logFile is open, close it.
function logClose () {
	logStop();
	if (logFile != null) {
		logFile.close();
	}		
}

// If specified, set up the patterns to be used for inclusive matching - only properties
// that explicitely match these patterns will be logged.
// Exception: The "level 0" property is a comment provided by the user and the value of
// the initial item passed to logIt() will always be logged.
function logSetDoFollowPatterns (patternStrings) {
	logDoFollowPatterns = logSetPatterns (patternStrings);
//	alert ("logDoFollowPatterns = " + patternStrings);
}

function wasLogSetDoFollowPatterns (patternStrings) {
	var i, n, s, x;
	if ((null == patternStrings) || (0 == patternStrings.length)) {
		logDoFollowPatterns = null;
	} else {
		logDoFollowPatterns = [];
		i = 0;
		n = patternStrings.length;
		for (i = 0; i < n; i++) {
			s = patternStrings[i];
			x = logNewRegExp(s);
			logDoFollowPatterns[i] = x;
		}
		// Hack in the patterns to follow first three array/collection
		// elements as they would normally be excluded from positive
		// matching.
//		for (i = 0; i < 3; i++) {
//			x = new RegExp(i.toString());
//			logDoFollowPatterns[n + i] = x;
//		}
	}
}

// If specified, set up the patterns to be used for inclusive matching - any properties
// that match these patterns will be excluded from the log.
// Exception: The "level 0" property is a comment provided by the user and the value of
// the initial item passed to logIt() will always be logged.
function logSetDoNotFollowPatterns (patternStrings) {
	logDoNotFollowPatterns = logSetPatterns (patternStrings);
//	alert ("logDoNotFollowPatterns = " + patternStrings);
}
	
function logSetPatterns (patternStrings) {
	var patterns = null;
	if (null != patternStrings) {
		var i, n, s, x;
		patterns = [];
		i = 0;
		n = patternStrings.length;
		for (i = 0; i < n; i++) {
			s = patternStrings[i];
			x = new RegExp("[...]*" + s + "[...]*", "i");
			patterns[i] = x;
		}
	}
	return patterns;
}

// If the limits allow, open an alert dialog and/or embed an alert tag in the log.
function logAlert (string) {
	logAlerts++;
	if (logAlerts <= logAlertLimit) {
		alert (string + " : " + logBuffer);
	}
	if (logAlerts <= logTagLimit) {
		logBuffer = logBuffer + " <<" + string + ">> ";
	}
}

// Still need this? XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
function logAlertForced (string) {
	alert (string);
}

// Still need this? XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
function logMsg (string) {
	toConsole("in logMsg");
	if (logMethod != null) {
		logToMethod(string);
		// logBuffer = string;
		// logLine();
	}
}

// Log the entity/property specified. If property is specified, it is generally the name of the
// if the property within a containing entity. It may also be a user comment providied at "level 0".
function logIt (it, property) {
//	logBuffer = ""; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
	var type;
	// logAlert("logIt a");
	if ((null != logMethod) && (false != logFollow(property)) && (logIndentation <= logMaxIndentation)) {
		// logAlert ("logIt(" + it + ", " + prefix + ")");
//if ( logLines < 10) alert("183: property = " + property + ", type = " + logType(it) + ", value = " + logValue(it) + ", logIndentation = " + logIndentation);
		if (property != null) {
			logBuffer = logBuffer + property + ": ";
		}
		try {
			if (null == it) {
				// logAlert("logIt null");
				logBuffer = logBuffer + "null";
			} else if (undefined == it) {
				// logAlert("logIt undefined");
				logBuffer = logBuffer + "undefined";
			} else {
				// logAlert("logIt b");
/*
				if (logIsRecursive(it)) {
					logBuffer = logBuffer + "RECURSIVE (recursion not followed)";;
					logLine();
				}
*/    
				// Process various possibilities in order of likelihood.
				logItByType (it);
			}
		} catch (ex) {
			logAlert("logIt d");
			// Quietly ignore failure to getElements. - NOT
			logBuffer = logBuffer + " EXCEPTION[logIt](" + type + "): " + ex.toString();
			logLine();
		}
	}
	// logAlert("logIt y");
	logLine();
	// logAlert("logIt z");
}

// Log each type of property (String, Collection, Array, Number, Boolean, Function)
// in a manner appropriate to its type.
function logItByType (it) {
	var type = logType (it);
// if ( logLines < 10) alert("221: property = " + property + ", type = " + type + ", value = " + value + ", logIndentation = " + logIndentation);
	// Process various possibilities in order of likelihood.
	if (null == type) {
		// Do nothing.
	} else if("String" == type) {
		logString(it);
	} else if ("Number" == type) {
		logNumber(it);
	} else if ("Object" == type) {
		logBuffer = logBuffer + type + " ";
		logObject(it);
	} else if ("Collection" == type) {
		logBuffer = logBuffer + type + " ";
		logCollection(it);    
	} else if ("Array" == type) {
		logBuffer = logBuffer + type + " ";
		logArray(it);
	} else if ("Boolean" == type) {
		logBoolean(it);
	} else if ("Function" == type) {
		if (false) {
			logBuffer = logBuffer + type + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<";
			// Do nothing at this time.
		}
	} else if ("Special String" == type) {
		logSpecialString(it);
	}
	logLine();
}

// Support functions in alphabetical order in this section.

// Log an array by treating each index as a child property of the array.
function logArray (array) {
    // logAlert("logArray a");
    try {
        logBuffer = logBuffer + logName(array);     
        // logAlert("logArray b");
        logArrayElements (array);
        // logAlert("logArray c");
    } catch (ex) {
        // logAlert("logArray d");
        // Quietly ignore failure to getElements. - NOT
        logBuffer = logBuffer + "EXCEPTION[logArray]: " + ex.toString();
        logLine();
    }
    // logAlert("logArray z");
    
}

function logArrayElements (array) {
    logAlert("logArrayElements a");
    var i, item, shortIndent, longIndent;
    var temp;
    var size = 0;
    try { temp = array.length; size = temp; } catch (ex) { } // logAlert("No length"); }
    logBuffer = logBuffer + " [" + size + "]: ";
    logAlert("logArrayElements b");
    if (logIndentation < logMaxIndentation) {
        logLine();
        logAlert("logArrayElements c");
        shortIndent = getIndent("A");
        // pushIndent();
        longIndent = getIndent("B");
        logAlert("logArrayElements d - size = " + size);
        if (logBrief) size = logElementLimit;
        for(i = 0; i < size; i++) {
            logAlert("logArrayElements e");
            item = array[i];
            if (array[i] == undefined) {
                logBuffer = logBuffer + shortIndent + logElementIndex(i) + " UNDEFINED";
            } else {
                logAlert("logArrayElements f - " + array[i]);
                logBuffer = logBuffer + shortIndent + logElementIndex(i);
                logIt(item);
                logAlert("logArrayElements g");
            }
            logAlert("logArrayElements h");
        }
        // popIndent();
    } else {
        logBuffer = logBuffer + " MAX INDENTATION (not followed)";
        logLine();
    }
    logAlert("logArrayElements z");
}

function logBoolean (bool) {
    // logAlert("logBoolean a");
    logBuffer = logBuffer + bool.toString();
    // logAlert("logBoolean z");
}

function logCollection (collection) {
    logAlert("logCollection a");
    try {
        logBuffer = logBuffer + logName(collection);
        logAlert("logCollection b");
        logCollectionElements(collection);
        logAlert("logCollection c");
    } catch (ex) {
        logAlert("logCollection d");
        // Quietly ignore failure to getElements. - NOT
        logBuffer = logBuffer + "EXCEPTION[logCollection]: " + ex.toString();
        logLine();
    }
    logAlert("logCollection z");
}

function logCollectionElements (collection) {
    logAlert("logCollectionElements a");
    var i, item, shortIndent, longIndent;
    var temp;
    var size = 0;
    try { temp = collection.count(); size = temp; } catch (ex) { } // logAlert("No count"); }
    logBuffer = logBuffer + " [" + size + "]: ";
    logAlert("logCollectionElements b");
    if (logIndentation < logMaxIndentation) {
        logLine();
        shortIndent = getIndent("C");
        // pushIndent();
        longIndent = getIndent("D");
        logAlert("logCollectionElements c");
        i = 1;
        item = collection.firstItem();
        while (true) {
            logAlert("logCollectionElements d - i = " + i);
            if (item === null) break;
            if (logBrief && (i >= logElementLimit)) break;
            logBuffer = logBuffer + shortIndent + logElementIndex(i);
            logIt(item);
            i++;
            item = collection.nextItem(item);
        }
        // popIndent();
    } else {
        logBuffer = logBuffer + " MAX INDENTATION (not followed)";
        logLine();
    }
    logAlert("logCollectionElements z");
}

function logDefault (object) {
    // logAlert("logDefault a");
    logName (object);
    // logAlert("logDefault z");
}

function logNumber (number) {
    // logAlert("logNumber a");
    logBuffer = logBuffer + number;
    // logAlert("logNumber z - " + logBuffer);
}

function logObject (object) {
    logAlert("logObject a");
    
    if (object == undefined) {
        // Do nothing
        logAlert ("logObject b: object == undefined in logObject");
    } else {
    		logAlert ("logObject c");
        logBuffer = logBuffer + logName(object);
        logAlert ("logObject d: name = " + logName(object));
        if (logIsRecursive(object)) {
        		logAlert ("logObject e");
            logBuffer = logBuffer + " ALREADY LOGGED (not followed)";
            logLine();
        } else {
        		logAlert ("logObject f");
            if (logIndentation < logMaxIndentation) {
            		logAlert ("logObject g");
                logLine();
                pushIndent();
                logPushRecursionList(object);
                logAlert ("logObject h");
                // logRecursionList.push(object);
                //logAlert ("logObject: push(" + object.name +")");
                // logAlert("logObject b");
                logObjectProperties(object);
                logAlert ("logObject i");
                logPopRecursionList();
                logAlert ("logObject j");
                // logRecursionList.pop();
                //logAlert ("logObject: pop(" + object.name +")");
                popIndent();
                logAlert ("logObject k");
            } else {
            		logAlert ("logObject y");
                logBuffer = logBuffer + " MAX INDENTATION (not followed)";
                logLine();
            }
        }
    }
    logAlert("logObject z");
}

function logPushRecursionList (object) {

    logRecursionList.push(object);
    logRecursionListLength++;
}

function logPopRecursionList () {
    // logRecursionList.pop();
    logRecursionListLength--;
    if (logRecursionListLength == 0) {
        logRecursionList = new Array();
    }
}

function wasLogObject (object) {
    logAlert("logObject a");
    // var parent;
    // parent = object.parent;
    logAlert ("logObject: parent = " + parent);
    if (true) { // (parent != undefined) {
        logRecursionList.push(object); // parent);
        // logAlert ("logObject: push(" + object.name +")");
    }
    logBuffer = logBuffer + logName(object);
    logAlert("logObject b");
    logObjectProperties(object);
    if (true) { //(parent != undefined) {
        logRecursionList.pop();
        // logAlert ("logObject: pop(" + object.name +")");
    }
    logAlert("logObject z");
}

function logObjectProperties (object) {
	var property, value;
	var excluded = [];
	var notIncluded = [];
	
	for (property in object) {
		if (property in ["properties", ,"prototype"]) { //== "properties") {
			// Just ignore this redundant distraction.
		} else {
			try {
				value = object[property];
			} catch (ex) {
				// Window.alert("property = " + property + " and we have an exception");
				value = " [ " + ex.toString() + " ] ";
			}
			try {
				logFlush();
				var logFollowProperty = logFollowNew(property);
				if (logFollowProperty == false) {
					excluded.push(property);
				} else if (logFollowProperty == null) {
					notIncluded.push(property);
				} else { // if (logFollowProperty == true)
					logBuffer = logBuffer + getIndent("E3");
					logIt (value, property);
				}
			} catch (ex) {
				logBuffer = logBuffer + "EXCEPTION[logObjectProperties]: " + ex.toString();
				logLine();
			}
		}
	}
	logLine();
	if (logVerbose) {
		if (0 < excluded.length) {
			logBuffer = getIndent("E4") + "EXCLUDED = " + excluded.join(", ");
			logLine();
		}
		if (0 < notIncluded.length) {
			logBuffer = getIndent("E5") + "NOT INCLUDED = " + notIncluded.join(", ");
			logLine();
		}
	}
}



function wasLogObjectPropertiesModifed (object) {
    var property, value; // , parent; // alreadyLogged;
    // logAlert("logObjectProperties a");
    var excluded = [];
    var notIncluded = [];
    
    for (property in object) {
			var logFollowProperty = logFollow(property);
			if (logFollowProperty != false) {
        try {
            value = object[property];
        } catch (ex) {
            // Window.alert("property = " + property + " and we have an exception");
            value = " [ " + ex.toString() + " ] ";
        }

        // logAlert("logObjectProperties b");
        try {
/*
            if (property == "hiddenTexts") {
                logTagLimit = 50;
            }
*/
            if (property == "properties") {
                // Just ignore this redundant distraction.
            } else {
/*
                alreadyLogged = false;
                if (object.hasOwnProperty("loggedByVMAt")) {
                    if (object.loggedByVMAt == logVMAt) {
                        alreadyLogged = true;
                    }
                }
*/
                // logBuffer = logBuffer + getIndent("E") + property + ": ";
                logFlush();
                // logAlert("logObjectProperties c");
                var logFollowProperty = logFollowNew(property);
                if (logFollowProperty == false) {
                	if (logVerbose) {
                		logBuffer = logBuffer + getIndent("E2") + property + ": ";
                    logBuffer = logBuffer + logName(value) + " EXCLUDED PATTERN (not followed) logBuffer = >>>" + logBuffer + "<<< 503";
                  }
                    logLine();
                    // logAlert("logObjectProperties d");
	              } else if (logFollowProperty == null) {
	              	if (logVerbose) {
	              		logBuffer = logBuffer + getIndent("E3") + property + ": ";
                    logBuffer = logBuffer + logName(value) + " NOT INCLUDED PATTERN (not followed) logBuffer = >>>" + logBuffer + "<<< 510";
                  }
                    logLine();
                    // logAlert("logObjectProperties d");
/*
                } else if (alreadyLogged) {
                    logBuffer = logBuffer + "ALREADY LOGGED";
                    logLine();
*/
/*
                } else if (value == object) {
                    logBuffer = logBuffer + "THIS (recursion on self not followed)";
                    logLine();
*/
/*
                } else if (logIsRecursive(value)) {
                    logBuffer = logBuffer + "RECURSIVE (recursion not followed)";;
                    logLine();
*/
								} else { // if (logFollowProperty == true)
                    // logAlert("logObjectProperties e");
/*
                    object.loggedByVMAt = logVMAt;
*/
                    logBuffer = logBuffer + getIndent("E3");
                    logIt (value, property);
                    // logAlert("logObjectProperties f");
                }
            }
        } catch (ex) {
            logBuffer = logBuffer + "EXCEPTION[logObjectProperties]: " + ex.toString();
            logLine();
            // logAlert("logObjectProperties g - ex = " + ex);
        }
    }
  }
    // logAlert("logObjectProperties h - logBuffer = " + logBuffer);

    // logAlert("logObjectProperties z");
}

function wasOldLogObjectProperties (object) {
    var property, value;
    logAlert("logObjectProperties a");
    pushIndent();
    if (logIndentation <= logMaxIndentation) {
        for (property in object) {
            try {
                value = object[property];
            } catch (ex) {
                // Window.alert("property = " + property + " and we have an exception");
                value = " [ " + ex.toString() + " ] ";
            }
            logAlert("logObjectProperties b");
            try {
                if (property == "properties") {
                    // Just ignore this redundant distraction.
                } else {
                    logBuffer = logBuffer + getIndent("E") + property + ": ";
                    logFlush();
                    logAlert("logObjectProperties c");
                    if (logDoNotFollow(property)) { // (property == "parent") {
                        logBuffer = logBuffer + logName(value) + " EXCLUDED PATTERN (not followed)";
                        logLine();
                        logAlert("logObjectProperties d");
                    } else if (value == object) {
                        logBuffer = logBuffer + "THIS (recursion on self not followed)";
                        logLine();
                    } else {
                        logAlert("logObjectProperties e");
                        logIt (value);
                        logAlert("logObjectProperties f");
                    }
                }
            } catch (ex) {
                logBuffer = logBuffer + "EXCEPTION[logObjectProperties]: " + ex.toString();
                logLine();
                logAlert("logObjectProperties g - ex = " + ex);
            }
        }
        logAlert("logObjectProperties h - logBuffer = " + logBuffer);
    }
    popIndent();
    logAlert("logObjectProperties z");
}

function logSpecialString (string) {
    // logAlert("logSpecialString a");
    logBuffer = logBuffer + string + "\n";
    logLine();
    // logAlert("logSpecialString z");
}

function logString (string) {
    // logAlert("logString a");
    logBuffer = logBuffer + "\"" + string + "\"";
    // logAlert("logString z");
}

// Utility functions in logical order.

// xxx
function logElementIndex(index) {
	var str = index.toString() + ":";
	var need = logIndentationMultiplier - str.length;
	if (need > 0) {
		var start = logIndentationText.length - need;
		str = str + logIndentationText.slice(start);
	}
	return str;
}

// Determine if a property should be logged
function logFollow(property) {
	return true;
}
function logFollowNewNotQuiteRight(property) {
	return logFollowNotQuiteRight(property);
}
function logFollowNew(property) {
	var include = logDoFollow(property);
	var excludeNot = !logDoNotFollow(property);
	var result = null;
	// Check for explicitely included but not excluded ...
	if (include && excludeNot) {
		// ... follow this property.
		result = true;
	} else {
		// Otherwise, follow if not explicitly excluded.
		result = excludeNot;
	}
	if (null == include) result = excludeNot;
	else result = include && excludeNot;
// if (10 > logLines) alert("639: property = " + property + ", include = " + include + ", excludeNot = " + excludeNot + ", result = " + result);
// if (2 > logLines) alert("640: if(null) = " + ifNull());
// if (2 > logLines) alert("641: if([null]]) = " + ifEmpty());
// if (2 > logLines) alert("642: logDoFollowPatterns = " + logDoFollowPatterns);
// if (2 > logLines) alert("643: logDoNotFollowPatterns = " + logDoNotFollowPatterns);
	return result;
}
function ifNull () {
	if (null) return true;
	return false;
}
function ifEmpty() {
	if ([]) return true;
	return false;
}
function logFollowNotQuiteRight(property) {
//	if (0 == logIndentation) return true; // Special case to exempt user comment.
	if (null != logDoFollowPatterns) {
		if (logDoFollow(property)) {
			return !logDoNotFollow(property);
		}
	}
	if (null != logDoNotFollowPatterns) return !logDoNotFollow(property);
	// alert("logFollow(" + property + ") = true");
	return true;
}

function logDoFollow(property) {
	if (null == logDoFollowPatterns) return null;
	var i, n, p, r, ps, rs;
	// logAlert("logFollow a");
	// i = 0;
	n = logDoFollowPatterns.length;
	// logAlert("logFollow b: n = " + n);
	for (i = 0; i < n; i++) {
		// logAlert("logFollow c: i = " + i, ", n = " + n);
		p = logDoFollowPatterns[i];
		// logAlert("logFollow d");
		r = p.test(property);
		// r = (property.search(p) > -1);
		// logAlert ("logFollow e");
		// logAlert ("property = \"" + property + "\", p.toString() = " + p.toString() + "r = " + r); 
		if (r) {
			// logAlert("logFollow true");
			// alert("logDoFollow(" + property + ") = true");
			return true;
		}
	}
	// logAlert("logDoFollow not true - fall back on logDoNotFollow");
	return false;
}

function logDoNotFollow(property) {
	if (null == logDoNotFollowPatterns) return false;
	var i, n, p, r, ps, rs;
	// logAlert("logDoNotFollow a");
	// i = 0;
	n = logDoNotFollowPatterns.length;
	// logAlert("logDoNotFollow b: n = " + n);
	for (i = 0; i < n; i++) {
		// logAlert("logDoNotFollow c: i = " + i, ", n = " + n);
		p = logDoNotFollowPatterns[i];
		// logAlert("logDoNotFollow d");
		r = p.test(property);
		// r = (property.search(p) > -1);
		// logAlert ("logDoNotFollow e");
		// logAlert ("property = \"" + property + "\", p.toString() = " + p.toString() + "r = " + r); 
		if (r) {
			// logAlert("logDoNotFollow true");
			// alert("logDoNotFollow(" + property + ") = true");
			return true;
		}
	}
	// logAlert("logDoNotFollow false");
	// alert("logDoNotFollow(" + property + ") = false");
	return false;
}

function logIsRecursive (object) {
    var i, n, o;
    // logAlert("logIsRecursive a");
    // i = 0;
    n = logRecursionList.length;
    // logAlert("logIsRecursive b: n = " + n);
    for (i = 0; i < n; i++) {
        o = logRecursionList[i];
        // // logAlert("logIsRecursive c: i = " + i + ", n = " + n + ", object = " + object.name + ", o = " + o.name);
        // logAlert("logIsRecursive d");
        if (object == o) {
            // logAlert("logIsRecursive: object = true");
            // logAlert("logIsRecursive: object = " + object.name + " true");
            return true;
        }
    }
    // logAlert("logIsRecursive: object = false");
    // logAlert("logIsRecursive: object = " + object.name + " false");
    return false;
}

function logName (obj) {
    // logAlert("logName a");
    var name = "";
    var description = "";
    var constructor = "";
    var temp;
    var string;
    if (obj == "undefined") {
        string = "UNDEFINED";
    } else {
        // try { temp = obj.name; name = logNoUndefName(temp); } catch (ex) { } // logAlert("No name"); }
        // try { temp = obj.description; description = logNoUndefDesc(temp); } catch (ex) { } // logAlert("No description"); }
        // try { temp = obj.constructor; constructor = logConstructorName(temp); } catch (ex) { } // logAlert("No constructor"); }
        name = logFirstProperty(obj,
                                ["name", "id"],
                                null); // "unnamed");
        description = logFirstProperty(obj,
                                       ["description", "documentPath"],
                                       null); // "");
        description = logWrappedIn (" \"", description, "\"");
        constructor = logConstructorName(logFirstProperty(obj,
                                                          ["constructor"],
                                                          "undefined"));
        constructor = logWrappedIn (" [", constructor, "]")
        string = name + description + constructor;
    }
    // logAlert("logName z: " + string);
    return string;
}

function logWrappedIn (prefix, string, suffix) {
	if (null == string) return "";
	return prefix + string + suffix;
}

function logFirstProperty (object, propertyList, undefinedString) {
    var result, i, n, property;
    var topl;
    topl = (typeof propertyList);
    // Window.alert ("logFirstProperty: (typeof propertyList) = " + topl); //  + propertyList.toString());
    i = 0;
    n = propertyList.length;
    while (true) {
        // Window.alert ("before: propertyList.length");
        // Window.alert ("i = " + i);
        // Window.alert ("length = " + propertyList.length); // Throws TypeError exception
        if (i == propertyList.length) break;
        // Window.alert ("before: property = propertyList[i];");
        property = propertyList[i];
        // Window.alert ("after: property = propertyList[i];");
        i++;
        // Window.alert ("before: result = object[property]; property = " + property);
        try {
            result = object[property];
        } catch (ex) {
            result = "undefined";
        }
        // Window.alert ("after: result = object[property]; result = " + result);
        if (result != "undefined") break;
    }
    if (result == "undefined") {
        property = "N/A";
        result = undefinedString;
    }
    // Window.alert ("logFirstProperty: property = " + property + ", result = " + result);
    return result;
}

function logConstructorName (conFunc) {
    var string, start, end;
    var constructorName;
    // logAlert("string = " + string);
    if (conFunc == "undefined") {
        constructorName = "";
    } else {
        string = conFunc.toString();
        // Window.alert(string);
        //                      012345678
        start = string.indexOf("function ") + 9;
        // logAlert("start = " + start);
        end = string.indexOf("(");
        // logAlert("end = " + end);
        constructorName = string.substring(start, end);
    }   
    // logAlert("constructorName = " + constructorName);
    return constructorName;
}

function DisplayPropertyNames(obj) {
    var names = "";
    for(var name in obj) names += name + "\n";
    alert(names);
} 

function logType (it) {       // Array, Boolean, Function, Number, String, or just Object
		logAlert("logType a: it = " + it.toString());
    var x;
    var type = (typeof it);
    logAlert("logType b: type = " + type);
    if (type = "object") {
        type = "Object";

        // Try various possibilities in order of likelihood.

        try {
        		// logAlert("logType c");
            it.substr(0,0);
            type = "String";
            var last = it.length - 3;
            if ((it.indexOf(" [ ", 0) == 0) && (it.indexOf(" ] ", last) == last)) {
                type = "Special String"
            }
        } catch (ex) {
        		// logAlert("logType d");
            // Quietly do nothing as the object is not a String.
        }
        if (type == "String") return type;

        try {
        		// logAlert("logType e");
            x = it.firstItem();
            type = "Collection";
            // logAlert("type [B] = " + type);
        } catch (ex) {
        		// logAlert("logType e");
            // Quietly do nothing as the object is not really a Collection.
        }
        if (type == "Collection") return type;

        try {
        		// logAlert("logType f");
            it.push(it.pop());
            type = "Array";
        } catch (ex) {
        		// logAlert("logType g");
            // Quietly do nothing as the object is not really an Array.
        }
        if (type == "Array") return type;

        try {
        		// logAlert("logType h");
            x = it.toExponential(2);
            type = "Number";
        } catch (ex) {
        		// logAlert("logType i");
            // Quietly do nothing as the object is not really a Number.
        }
        if (type == "Number") return type;

        try {
        		// logAlert("logType j");
            x = it.toString();
            if ((x == "true") || (x == "false")) {
                type = "Boolean";
            }
        } catch (ex) {
        		// logAlert("logType k");
            // Quietly do nothing as the object is not really a Boolean.
        }
        if (type == "Boolean") return type;

        try {
        		// logAlert("logType l");
            // x = it.arguments.length;
            x = logConstructorName(logFirstProperty(it, ["constructor"], "undefined"));
            if ("Function" != x) throw ("not a function");
            type = "Function";
        } catch (ex) {
        		// logAlert("logType m");
            // Quietly do nothing as the object is not really a Function.
        }
        if (type == "Function") return type;

        logAlert("XXXXXXXXXX UNKNOWN OBJECT PASSED TO logType: it = " + it.toString());
    } else {
        logAlert("XXXXXXXXXX NON-OBJECT PASSED TO logType");
    }
    return type;
}

function logValue(object, property) {
//if ( logLines < 10) alert("842: object = " + object + ", property = " + property + ", logIndentation = " + logIndentation);
	if (null == property) return null;
	var value;
	if (0 == logIndentation) {
		// Special case the "level 0" property as a comment.
		return object;
	} else {
		try {
			value = object[property];
		} catch (ex) {
			// Window.alert("property = " + property + " and we have an exception");
			value = " [ " + ex.toString() + " ] ";
		}
	}
//if ( logLines < 10) alert("856: value = " + value + ", logIndentation = " + logIndentation);
	return value;
}

function logFlush() {
    // logAlert("logFlush a: " + logBuffer);
    if (logMethod != null) {
        // Flush buffer to capture every scrap of available information.
        if (true) { // (logBuffer.length > 0) {
            logBuffer = logBuffer;
            logToMethod(logBuffer);
            logBuffer = "";
        }
    }
    // logAlert("logFlush z: " + logBuffer);
}

function logLine() {
    // logAlert("logLine a: " + logBuffer);
    if (logMethod != null) {
        // Flush every line to file to prevent memory problems.
        if (logBuffer.length > 0) { //true) { // (logBuffer.length > 0) {
            logToMethod(logBuffer + "\n");
            logBuffer = ""; 
        }
        // logAlert("logLine b " + logLines + " / " + logLineLimit);
        // If logLineLimit is reached, abort the whole script.
        if (logLines > logLineLimit) {
            // logAlert("logLine c");
            logToMethod("\n\n logLineLimit reached - we're done.");
            logClose();
            // logAlert("logLineLimit reached - we're done");
            throw "logLineLimit reached - we're done";
            // logAlert("logLine d");
        }
    }
    // logAlert("logLine z: " + logBuffer);
}

function logLineNumber() {
	var maxString = "" + logLineLimit;
	var maxSize = maxString.length;
	logLines++;
	var paddedNumber = "0000000000" + (logLines);
	var end = paddedNumber.length;
	var start = end - maxSize;
	paddedNumber = paddedNumber.slice(start, end);
	// alert ("padded number = " + paddedNumber + " of " + maxString);
	return paddedNumber;
}

function pushIndent () {
    logIndentation = logIndentation + 1;
}

function popIndent () {
    logIndentation = logIndentation - 1;
}

function getIndent (tag) {
    var len;
    var s = "";
    // logAlert("getIndent a: " + logBuffer);
    if (logMethod != null) {
        // Insert tag in log stream.
        // logBuffer = logBuffer + "<" + tag + ">";
        // logAlert("getIndent b");
        // Compute the indentation to be used at the start of the line.
        len = logIndentation * logIndentationMultiplier;
        if (len > 0) {
            s = logIndentationString.substr(0,len);
        } else {
            s = "";
        }
    }
    // logAlert("getIndent z");
    return s;
}

var EOL = "\n"; "\r\n|\r|\n"; // "e"; // "\\n";
var methodBuffer = "";
function logToMethod (str) {
//	alert("in logToMethod methodBuffer = " + methodBuffer);
	methodBuffer = methodBuffer + str;
	var lines = methodBuffer.split(EOL); // ("\r\n|\r|\n");
	var numLines = lines.length;
	var singleLine;
//	alert("in logToMethod numLines = " + numLines);
//	alert("numLines = " + numLines);
	if (numLines > 1) {
		for (i = 0; i < (numLines - 1); i++) {
//			alert("in logToMethod for loop: i = " + i + ", numLines = " + numLines);
			// Send all terminated lines to the console.
			singleLine = logLineNumber() + ":" + logIndentationText.charAt(logIndentationText.length - 1) + lines[i];
//			alert("singleLine = " + singleLine);
			logMethod(singleLine);
		}
//		alert("out of logToMethod for loop");
		// Leave the trailing fragment as the new consoleBuffer.
		methodBuffer = lines[numLines-1];
	}
}

function Male() {
}
function Female() {
}

function logTester(lineWriter) {
	toConsole("logTester - Set up test data.");
		var family, child_1, child_2, parent_M, parent_F,
		    grandparent_M_M, grandparent_M_F, grandparent_F_M, grandparent_F_F;
		grandparent_M_M = new Male();
		grandparent_M_M.name = "George";
		grandparent_M_M.description = "Paternal Grandfather";
		grandparent_M_F = new Female();
		grandparent_M_F.id = "grandparent_M_F";
		grandparent_M_F.description = "Paternal Grandmother";
		grandparent_F_M = new Male();
		grandparent_F_M.name = "Charles";
		grandparent_F_M.description = "Maternal Grandfather";
		grandparent_F_F = new Female();
		grandparent_F_F.id = "grandparent_F_F";
		grandparent_F_F.description = "Maternal Grandmother";
		parent_M = new Male();
		parent_M.name = "Vernon";
		parent_M.description = "Father";
		parent_M.parents = [grandparent_M_M, grandparent_M_F];;
		parent_M.papa = grandparent_M_M;
		parent_F = new Female();
		parent_F.name = "Phyllis";
		parent_F.description = "Mother";
		parent_F.parents = [grandparent_F_M, grandparent_F_F];;
		parent_F.papa = grandparent_M_F;
		uncle_1 = new Male();
		uncle_1.name = "Dean";
		uncle_1.description = "Uncle";
		uncle_1.parents = [grandparent_M_M, grandparent_M_F];;
		uncle_1.dad = grandparent_M_M;
		uncle_2 = new Male();
		uncle_2.name = "George";
		uncle_2.description = "Uncle";
		uncle_2.parents = [grandparent_M_M, grandparent_M_F];;
		uncle_2.pops = grandparent_M_M;
		child_1 = new Male();
		child_1.name = "Vern";
		child_1.id = "First Born";
		child_1.parent = parent_M;
		child_1.parents = [parent_M, parent_F];
		child_1.grandparents = [grandparent_M_M, grandparent_M_F, grandparent_F_M, grandparent_F_F];
		child_1.gramps = grandparent_M_M;
		child_1.gramma = grandparent_F_F;
		child_1.dad = parent_M;
		child_1.mom = parent_F;
		child_2 = new Male();
		child_2.id = "Second Born";
		child_2.name = "Mike";
		child_2.parent = parent_F;
		child_2.parents = [parent_M, parent_F];
		child_2.grandparents = [grandparent_M_M, grandparent_M_F, grandparent_F_M, grandparent_F_F];
		child_2.grammy = grandparent_F_F;
		child_2.daddy = parent_M;
		parent_M.children = [child_1, child_2];
		parent_F.children = [child_1, child_2];
		grandparent_M_M.children = [parent_M, uncle_1, uncle_2];
		grandparent_M_F.children = [parent_M, uncle_1, uncle_2];
		grandparent_F_M.children = [parent_F];
		grandparent_F_M.children = [parent_F];
		family = [grandparent_M_M, grandparent_M_F, grandparent_F_M, grandparent_F_F];
		var lineLimit = null;
		var indentationLimit = null;
		var alertLimit = null;
		var tagLimit = null;
		var doLog = null;
		var doNotLog = null;
		var brief = null;
	toConsole("logTester - 1 - Log it all in the default manner.");
		logStart(lineWriter);
		logIt(family);
		logStop();
	toConsole("logTester - 2 - Log part of the tree with different limits.");
		lineLimit = 999;
		indentationLimit = 3;
		logStart (lineWriter, lineLimit, indentationLimit, alertLimit, tagLimit, doLog, doNotLog, brief);
		logIt(parent_M, "parent_M");
		logStop();
	toConsole("logTester - 3 - Log again, excluding 'parent'.");
		doNotLog = ["parent"];
		logStart(lineWriter, lineLimit, indentationLimit, alertLimit, tagLimit, doLog, doNotLog, brief);
		logIt(parent_M);
		logStop();
	toConsole("logTester - 4 - Log again, including only a few properties - including 'parent' which should be excluded.");
		doLog = ["name", "child", "parent"];
		logStart(lineWriter, lineLimit, indentationLimit, alertLimit, tagLimit, doLog, doNotLog, brief);
		logIt(parent_M, "Do not include 'parent'");
		logStop();
	toConsole("logTester - 5 - Log again briefly.");
		brief = true;
		logStart(lineWriter, lineLimit, indentationLimit, alertLimit, tagLimit, doLog, doNotLog, brief);
		logIt(parent_M, "Log briefly");
		logStop();
	toConsole("logTester - 6 - Test no results if log is closed.");
		logStart(lineWriter, lineLimit, indentationLimit, alertLimit, tagLimit, doLog, doNotLog, brief);
		logClose();
		logIt(family, "Log closed");
		logStop();
  toConsole("logTester - Complete.");
}
